package com.lenin.metro.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.lenin.metro.shared.MapCoordinates;
import com.lenin.metro.shared.MapObject;
import com.lenin.metro.shared.ProjectionPoint;
import com.lenin.metro.shared.Tile;
import com.lenin.metro.shared.TileGroup;
import com.lenin.metro.shared.TileMetrics;
import com.lenin.metro.shared.TileMetrics.MetricsConfig;


public class GameFieldPanel extends AbsolutePanel {

	
	private List<Tile> tiles = new ArrayList<Tile>();
	private List<TileGroupPanel> groupPanels = new ArrayList<TileGroupPanel>();
	private List<MapObjectPanel> objectPanels = new ArrayList<MapObjectPanel>();

	private MapCoordinates centerCoords;

	private MetricsConfig defaultMetrics = MetricsConfig.S32W5H4;

	private int groupPanelWidth;
	private int groupPanelHeight;
	
	private MapCoordinates topLeftCoords;
	private MapCoordinates bottomRightCoords;
	
	private FocusPanel focusPanel;

	
	public GameFieldPanel() {

		focusPanel = new FocusPanel();
		focusPanel.setWidth("100%");
		focusPanel.setHeight("100%");
		focusPanel.getElement().getStyle().setZIndex(2);

		add(focusPanel, 0, 0);

		focusPanel.addClickHandler(new ClickHandler() {

			public void onClick(ClickEvent event) {

				int clickX = event.getX();
				int clickY = event.getY();

				System.out.println("click " + clickX + "/" + clickY);

				TileGroupPanel clickedPanel = getGroupPanelByPoint(new ProjectionPoint(
						clickX, clickY));
				MapCoordinates clickedPanelCoords = clickedPanel
						.getMapCoordinates();

				setCenter(clickedPanelCoords);
				
				refresh();


			}

		});

		init();

	}
	

	public void init() {
		
		TileMetrics metrics = defaultMetrics.getTileMetrics();
		
		int tileWidth =  metrics.getTileSize() * 2;
		int tileHeight = metrics.getTileSize();

		groupPanelHeight = tileHeight * metrics.getGroupHeight();
		groupPanelWidth = tileWidth * metrics.getGroupWidth();

	}
	

	public FocusPanel getFocusPanel() {
		return focusPanel;
	}
	

	public void setCenter(MapCoordinates centerCoords) {
		this.centerCoords = centerCoords;
	}
	

	public void refresh() {
		
		refreshObservableTiles();
		refreshObservableMapObjects();
		
	}
	
	
	public void redrawMapObjects() {

		for (MapObjectPanel panel : objectPanels) {

			ProjectionPoint location = getGameFieldLocation(panel.getMapCoordinates());

			panel.getElement().getStyle().setLeft(location.getX(), Unit.PX);
			panel.getElement().getStyle().setTop(location.getY(), Unit.PX);

		}

	}
	
	
	public void redrawTileGroups() {

		for (TileGroupPanel panel : groupPanels) {

			ProjectionPoint location = getGameFieldLocation(panel.getMapCoordinates());

			panel.getElement().getStyle().setLeft(location.getX(), Unit.PX);
			panel.getElement().getStyle().setTop(location.getY(), Unit.PX);

		}

	}

	
	public void refreshObservableTiles() {

		final List<MapCoordinates> missingCoords = new ArrayList<MapCoordinates>();
		List<TileGroupPanel> observableGroupPanels = new ArrayList<TileGroupPanel>();

		int halfWidth = (getOffsetWidth()) / 2;
		int halfHeight = (getOffsetHeight()) / 2;

		int hPanels = (((halfWidth + (groupPanelWidth / 2)) / groupPanelWidth) * 2) + 1;
		int vPanels = (((halfHeight + (groupPanelHeight / 2)) / groupPanelHeight) * 2) + 1;

		long centerLat = centerCoords.getLatitude();
		long centerLng = centerCoords.getLongitude();

		int startX = -(hPanels / 2) * groupPanelWidth; 
		int startY = -(vPanels / 2) * groupPanelHeight; 

		// System.out.println("startX="+startX+",startY="+startY);

		int x = startX;
		int y = startY;

		
		for (int i = 0; i < vPanels; i++) {

			x = startX;

			for (int j = 0; j < hPanels; j++) {

				ProjectionPoint dPoint = new ProjectionPoint(x, y);
				MapCoordinates dCoords = MapCoordinates.pointToCoords(dPoint);
				MapCoordinates coords = new MapCoordinates(centerLat
						+ dCoords.getLatitude(), centerLng
						+ dCoords.getLongitude());

				if(i == 0 && j == 0) {
					
					topLeftCoords = coords;
					
				} else if(i == vPanels-1 && j == hPanels - 1) {
					
					dPoint = new ProjectionPoint(x+groupPanelWidth, y+groupPanelHeight);
					dCoords = MapCoordinates.pointToCoords(dPoint);
					bottomRightCoords = new MapCoordinates(centerLat
							+ dCoords.getLatitude(), centerLng
							+ dCoords.getLongitude());
					 
				}
				
				TileGroupPanel panel = getGroupPanelByCoords(coords);
				if (panel != null) {
					observableGroupPanels.add(panel);
				} else {
					missingCoords.add(coords);
				}

				x += groupPanelWidth;

			}

			y += groupPanelHeight;

		}
		
		for (TileGroupPanel panel : groupPanels) {
			if (!observableGroupPanels.contains(panel)) {
				remove(panel);
			}
		}

		groupPanels = observableGroupPanels;

		redrawTileGroups();

		GreetingServiceAsync service = Metro2033.getService();
		
		for(MapCoordinates coords : missingCoords) {
			
			service.getTileGroupByCoords(coords, MetricsConfig.S32W5H4, new AsyncCallback<TileGroup>() {
			
				public void onSuccess(TileGroup tileGroup) {
					addTileGroup(tileGroup);
				}
			
				public void onFailure(Throwable caught) {
					System.out.println("service failure");
					caught.printStackTrace();
				}
			
			});
	
		}
		
		
	}
	
	
	public void refreshObservableMapObjects() {
		
		redrawMapObjects();
		
		final List<MapObjectPanel> observableObjectPanels = new ArrayList<MapObjectPanel>();
		
		GreetingServiceAsync service = Metro2033.getService();
		
		service.getMapObjectsByCoords(topLeftCoords, bottomRightCoords, new AsyncCallback<List<MapObject>>() {
			
			public void onSuccess(List<MapObject> mapObjects) {
				
				for(MapObject mapObject : mapObjects) {
					
					MapObjectPanel objectPanel = getObjectPanelByKey(mapObject.getKey());
					if(objectPanel == null) {
						objectPanel = addMapObject(mapObject);
					}
					
					observableObjectPanels.add(objectPanel);
				
				}
				
				for(MapObjectPanel mapObjectPanel : objectPanels) {
					
					if(!observableObjectPanels.contains(mapObjectPanel)) {
						
						remove(mapObjectPanel);
					
					}
					
				}
				
				objectPanels = observableObjectPanels;
				
			}
			
		
			public void onFailure(Throwable caught) {
				System.out.println("service failure");
				caught.printStackTrace();
			}
			
		});
		
	}
	
	
	private MapObjectPanel addMapObject(MapObject mapObject) {
		
		MapObjectPanel panel = new MapObjectPanel(mapObject);
		panel.build();

		ProjectionPoint location = getGameFieldLocation(panel.getMapCoordinates());

		add(panel, (int)location.getX(), (int)location.getY());

		objectPanels.add(panel);
		
		return panel;
		
	}
	

	private void addTileGroup(TileGroup tileGroup) {

		//System.out.println("Add group...");

		if (getGroupPanelByCoords(tileGroup.getCoordinates()) == null) {

			TileGroupPanel panel = new TileGroupPanel(tileGroup);
			panel.setWidth(groupPanelWidth);
			panel.setHeight(groupPanelHeight);
			panel.build();

			ProjectionPoint location = getGameFieldLocation(panel.getMapCoordinates());

			add(panel, (int)location.getX(), (int)location.getY());

			groupPanels.add(panel);

		}

	}
	

	
	private void addTileGroups(List<TileGroup> tileGroups) {

		for (TileGroup group : tileGroups) {

			addTileGroup(group);

		}

	}
	
	
	private ProjectionPoint getGameFieldLocation(MapCoordinates coords) {

		int centerX = (getOffsetWidth() - groupPanelWidth) / 2;
		int centerY = (getOffsetHeight() - groupPanelHeight) / 2;

		long dLat = coords.getLatitude() - centerCoords.getLatitude();
		long dLng = coords.getLongitude() - centerCoords.getLongitude();
		MapCoordinates dCoords = new MapCoordinates(dLat, dLng);

		ProjectionPoint point = MapCoordinates.coordsToPoint(dCoords);
		int x = centerX + (int)point.getX();
		int y = centerY + (int)point.getY();

		return new ProjectionPoint(x, y);

	}
	
	
	private TileGroupPanel getGroupPanelByCoords(MapCoordinates coords) {

		for (TileGroupPanel panel : groupPanels) {

			MapCoordinates panelChunkCoords = panel.getMapCoordinates();

			if (panelChunkCoords != null) {
				if (panelChunkCoords.getLatitude() == coords.getLatitude()
						&& panelChunkCoords.getLongitude() == coords
								.getLongitude()) {

					return panel;

				}
			}

		}

		return null;

	}

	
	private TileGroupPanel getGroupPanelByPoint(ProjectionPoint point) {

		int x = (int)point.getX();
		int y = (int)point.getY();

		for (TileGroupPanel panel : groupPanels) {

			int panelX = panel.getElement().getOffsetLeft();
			int panelY = panel.getElement().getOffsetTop();

			if (panelX < x && x < panelX + groupPanelWidth && panelY < y
					&& y < panelY + groupPanelHeight) {

				return panel;

			}

		}

		return null;

	}
	
	
	private MapObjectPanel getObjectPanelByKey(String key) {
		
		for (MapObjectPanel panel : objectPanels) {

			if (key.equals(panel.getObjectKey())) {
				return panel;
			}

		}

		return null;
		
	
	}
	

}
